﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace HtmlParser
{
    abstract class ByteReader
    {
		public delegate bool ByteTestMethod(int c);

		public ByteReader()
        {
            _Position = -1;
        }

        public const int ESCAPTE_CHAR = '\\';

		public abstract int Get(int index);
        public abstract int Read();
        public abstract int Back();
		public abstract int Peek();
		public abstract int PeekBack();

		public abstract int Length { get; }
        public abstract int Current { get; }

        protected int _Position = 0;
        public virtual int Position { get { return _Position; } }
        public virtual bool EOF { get { return _Position >= Length; } }

        private int snapPosition;
        public virtual void Snap()
        {
            snapPosition = _Position;
        }
        public virtual void RollBack()
        {
            _Position = snapPosition;
        }
        public virtual void Reset()
        {
            _Position = 0;
        }

		public abstract int To(string str);

		public virtual int To(ByteTestMethod m)
		{
			while (!EOF)
			{
				var c = Read();
				if (m(c))
				{
					return Position;
				}
			}
			return -1;
		}

		public virtual int To(int ch)
		{
			while (!EOF)
			{
				var c = Read();
				if (ch == c)
				{
					return Position;
				}
			}
			return -1;
		}
		public virtual int To(int ch, int escapte)
        {
            while (!EOF)
            {
                var c = Read();
                if (c == escapte)
                {
					_Position += 1;
					continue;
                }
                else if (ch == c)
                {
                    return Position;
                }
            }
            return -1;
        }
		public virtual int To(Token token)
		{
			while (!EOF)
			{
				var c = Read();
				if (token.IsMe(c))
				{
					return Position;
				}
			}
			return -1;
		}
		public virtual int To(Token token, int escape)
		{
			while (!EOF)
			{
				var c = Read();
				if (c == escape)
				{
					_Position += 1;
					continue;
				}
				if (token.IsMe(c))
				{
					return Position;
				}
			}
			return -1;
		}

		public static bool Contains(int ch, int[] chs)
        {
            return Array.IndexOf(chs, ch) > -1;
        }
        public virtual int[] OrTo(int[] chs, int startIndex)
        {
            _Position = startIndex;
            while (!EOF)
            {
                var c = Read();
                if (c == ESCAPTE_CHAR)
                {
                    this.Read();
                    continue;
                }
                else if (Contains(c, chs))
                {
                    return new int[] { Position - 1, c };
                }
            }
            return null;
        }

	}

	class ByteStringReader : ByteReader
    {
        public ByteStringReader(string s)
        {
            this.s = s;
        }
        private readonly string s;

        public override int Read()
        {
            _Position += 1;
            if (EOF) return 0;
			int c = s[_Position];
			return c;
        }

        public override int Peek()
        {
			return Get(_Position + 1);
		}
		public override int Back()
        {
            _Position -= 1;
			if (_Position < 0)
			{
				_Position = -1;
				return 0;
			}
			else if (_Position >= Length)
			{
				_Position = Length;
				return 0;
			}
            return s[_Position];
        }
		public override int PeekBack()
		{
			return Get(_Position - 1);
		}
		public override int Get(int index)
		{
			if (index < 0 || index >= Length)
			{
				return 0;
			}
			return s[index];
		}

		public override int Length
        {
            get { return s.Length; }
        }
        public override int Current
        {
            get 
            {
                var idx = _Position;
                if (idx < 0 || idx >= Length) return 0;
                return s[idx];
            }
        }

        public override int To(string str)
        {
            var r = new Regex(str, RegexOptions.IgnoreCase);
            var m = r.Match(s, Position);
            _Position = m.Success ? m.Index : Length;
            return _Position - 1;
        }


		public virtual int[] Read(HtmlTagTokenCollection tokens)
		{
			var stack = new Stack<HtmlTagToken>();

			var idx = new int[2];

			while (!EOF)
			{
				var c = Read();
				if (c == ESCAPTE_CHAR)
				{
					_Position += 1;
					continue;
				}
				var A = tokens.CheckA(c);
				if (A != null)
				{
					if (stack.Count < 1)
					{
						idx[0] = Position;
					}

					if (A.CanNest)
					{
						stack.Push(A);
						continue;
					}
					else
					{
						var b = To(A.b, '\\');
						if (b < 0)
						{
							idx[1] = Length - 1;
							return idx;
						}
						else
						{
							idx[1] = Position;
							if (stack.Count < 1)
							{
								return idx;
							}
						}
					}
				}
				else
				{
					if (stack.Count > 0)
					{
						var B = stack.Peek();
						if (B.IsB(c))
						{
							idx[1] = Position;
							stack.Pop();
							if (stack.Count < 1)
							{
								return idx;
							}
						}
					}
				}
			}
			return null;
		}

		public string PeekTagName()
		{
			Snap();
			var i0 = Position + 1;
			var i1 = To(delegate(int c) 
			{
				return c == '>' || Token.IsBlank(c);
			}) - 1;
			RollBack();
			if (i1 < i0) return null;
			var ss = s.Substring(i0, i1 - i0 + 1);
			return ss;
		}

	}
}
